package com.xingfly.order.model;

import com.xingfly.jpa.common.BaseEntity;
import com.xingfly.order.sdk.command.AddressCommand;
import com.xingfly.order.sdk.command.CreateOrderCommand;
import com.xingfly.order.sdk.event.OrderAddressChangedEvent;
import com.xingfly.order.sdk.event.OrderCreatedEvent;
import com.xingfly.order.sdk.event.OrderProductChangedEvent;
import com.xingfly.order.sdk.representation.AddressRepresentation;
import com.xingfly.order.sdk.representation.OrderItemRepresentation;
import com.xingfly.order.sdk.representation.OrderRepresentation;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.xingfly.order.model.OrderStatus.PAID;

/**
 * 订单表
 * Created by SuperS on 2019/11/7.
 *
 * @author SuperS
 */
@Data
@Entity
@Table(name = "t_order")
@EqualsAndHashCode(callSuper = true)
public class Order extends BaseEntity<OrderId> {

    public Order() {
        this.deleted = false;
    }

    /**
     * 一个值对象
     */
    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    /**
     * @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
     * @JoinColumn(name = "orderId")
     */
    @ElementCollection
    @CollectionTable(name = "t_order_items", joinColumns = {
            @JoinColumn(name = "orderId")
    })
    private List<OrderItem> items;

    private BigDecimal totalPrice;

    private Boolean deleted;

    public static Order create(CreateOrderCommand command) {
        Order order = new Order();
        order.setId(new OrderId());
        order.setStatus(OrderStatus.CREATED);
        order.setItems(command.getItems().stream().map(OrderItem::from).collect(Collectors.toList()));
        order.setAddress(Address.from(command.getAddress()));
        order.setCreateTime(LocalDateTime.now());
        order.calculateTotalPrice();
        order.registerEvent(new OrderCreatedEvent(order.getId().getId(), order.getTotalPrice(), order.getAddress().toCommand(),
                order.getItems().stream().map(OrderItem::to).collect(Collectors.toList()), order.getCreateTime()));
        return order;
    }


    public void calculateTotalPrice() {
        this.totalPrice = this.items.stream().map(OrderItem::totalPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public void changeProductCount(String productId, Integer count) {
        if (this.status == PAID) {
            throw new RuntimeException("Order Cannot Be Modified Exception");
        }
        OrderItem orderItem = this.items.stream().filter(item -> productId.equals(item.getProductId())).findFirst()
                .orElseThrow(() -> new RuntimeException("Product ID Not In The Order Exception"));
        Integer originCount = orderItem.getCount();
        orderItem.updateCount(count);
        calculateTotalPrice();
        registerEvent(new OrderProductChangedEvent(this.getId().getId(), productId, originCount, count));
    }


    public void changeAddress(AddressCommand addressCommand) {
        this.address = Address.from(addressCommand);
        registerEvent(new OrderAddressChangedEvent(this.getId().getId(), addressCommand));
    }

    /**
     * 移除订单项
     *
     * @param predicate 删除条件
     * @return 操作结果
     */
    public Boolean removeOrderItem(Predicate<OrderItem> predicate) {
        Boolean result = items.removeIf(predicate);
        if (result) {
            calculateTotalPrice();
        }
        return result;
    }

    public OrderRepresentation toRepresentation() {
        List<OrderItemRepresentation> orderItemRepresentations = this.getItems().stream()
                .map(orderItem -> new OrderItemRepresentation(orderItem.getProductId(), orderItem.getCount(), orderItem.getItemPrice()))
                .collect(Collectors.toList());
        AddressRepresentation address = this.address.toRepresentation();
        return new OrderRepresentation(this.getId().getId(), orderItemRepresentations, this.getTotalPrice(), this.getStatus().name(), address, this.getCreateTime());
    }

}
